//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 * JFFS2 -- Journalling Flash File System, Version 2.
 *
 * Copyright (C) 2001-2003 Red Hat, Inc.
 *
 * Created by Dominic Ostrowski <dominic.ostrowski@3glab.com>
 * Contributors: David Woodhouse, Nick Garnett, Richard Panton.
 *
 * For licensing information, see the file 'LICENCE' in this directory.
 *
 * $Id: flashio.cpp,v 1.7 2006/10/25 08:24:34 jianfeng Exp $
 *
 */
#include "ddk.h"
#include "nodelist.h"
#include "jffs2_misc.h"
#include "flashctrl.h"
#include "jffs2_iovec.h"

#define ALIGNSIZE 4

int jffs2_flash_read(struct jffs2_sb_info * c,
                uint32_t read_buffer_offset, const size_t size,
                size_t * return_size, unsigned char *write_buffer)
{
    ECode ec;
    struct super_block *sb;
    size_t readed = 0;

    if (!c || !write_buffer)
        return EINVAL;

    sb = OFNI_BS_2SFFJ(c);

    //flash driver request that size must be align with ALIGNSIZE
    if (size & (ALIGNSIZE-1)) {
        unsigned char lastBytes[ALIGNSIZE];
        size_t alignSize = size & (~(ALIGNSIZE-1));
        size_t unAlignSize = size - alignSize;

        if (alignSize > 0) {
            MemoryBuf buf((Byte *)write_buffer, alignSize);
            ec = sb->s_dev->Read(read_buffer_offset, alignSize, &buf, NULL);
            if (FAILED(ec))
                return ec;

            readed = buf.GetUsed();
            //if readed less than alignSize, we leave now.
            if (alignSize != readed) {
                goto Exit;
            }
        }
        //We have to read four bytes once
        Array((Byte *)lastBytes, ALIGNSIZE);
        ec = sb->s_dev->Read(read_buffer_offset+alignSize, ALIGNSIZE, &buf, NULL);
        if (FAILED(ec))
            return ec;

        unAlignSize = (size_t)((size_t)(buf.GetUsed()) >=
            unAlignSize) ? unAlignSize : buf.GetUsed();

        for (size_t i = 0; i < unAlignSize; i++) {
            *(write_buffer+alignSize+i) = lastBytes[i];
        }

        readed += unAlignSize;
    }
    else {

        MemoryBuf buf((Byte *)write_buffer, size);

        //D2(printf("FLASH READ\n"));
        //D2(printf("read address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + read_buffer_offset));
        //D2(printf("write address = %x\n", write_buffer));
        //D2(printf("size = %x\n", size));
        ec = sb->s_dev->Read(read_buffer_offset, size, &buf, NULL);
        if (FAILED(ec))
            return ec;

        readed = buf.GetUsed();
    }
Exit:
    if (return_size)
        *return_size = readed;

    return 0;
}

int jffs2_flash_write(struct jffs2_sb_info * c,
                uint32_t write_buffer_offset, const size_t size,
                size_t * return_size, unsigned char *read_buffer)
{

    ECode ec;
    struct super_block *sb;
    uint_t len;
    size_t writen = 0;

    if (!c || !read_buffer)
        return EINVAL;

    sb = OFNI_BS_2SFFJ(c);

    //flash driver request that size must be align with ALIGNSIZE
    if (size & (ALIGNSIZE-1)) {
        unsigned char lastBytes[ALIGNSIZE];
        size_t alignSize = size & (~(ALIGNSIZE-1));
        size_t unAlignSize = size - alignSize;
        if (alignSize > 0) {
            MemoryBuf buf((Byte *)read_buffer, alignSize);
            ec = sb->s_dev->Write(write_buffer_offset, buf, &len, NULL);
            if (FAILED(ec))
                return ec;
            writen = len;
            //if we write less than alignSize,leave now.
            if (writen != alignSize) {
                goto Exit;
            }
        }
        //We should read the last word for write back later
        MemoryBuf buf((Byte *)lastBytes, ALIGNSIZE);
        ec = sb->s_dev->Read(write_buffer_offset+alignSize, ALIGNSIZE, &buf, NULL);
        if (FAILED(ec))
            return ec;

        for (size_t i = 0; i < unAlignSize; i++) {
            lastBytes[i] = *(read_buffer+alignSize+i);
        }

        ec = sb->s_dev->Write(write_buffer_offset+alignSize, buf, &len, NULL);
        if (FAILED(ec))
            return ec;

        writen += (len >= unAlignSize)?unAlignSize:len;
    }
    else {

        MemoryBuf buf((Byte *)read_buffer, size);

        //    D2(printf("FLASH WRITE ENABLED!!!\n"));
        //    D2(printf("write address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + write_buffer_offset));
        //    D2(printf("read address = %x\n", read_buffer));
        //    D2(printf("size = %x\n", size));

        ec = sb->s_dev->Write(write_buffer_offset, buf, &len, NULL);
        if (FAILED(ec))
            return ec;
        writen = len;
    }
Exit:
    if (return_size)
        *return_size = writen;

    return 0;
}

int
jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct ELA_IOVEC_TAG *vecs,
                unsigned long count, loff_t to, size_t * retlen)
{
    unsigned long i;
    size_t totlen = 0, thislen;
    int err = 0;

    for (i = 0; i < count; i++) {
        // writes need to be aligned but the data we're passed may not be
        // Observation suggests most unaligned writes are small, so we
        // optimize for that case.

        if (((vecs[i].iov_len & (sizeof (int) - 1)))
            || (((unsigned long) vecs[i].
            iov_base & (sizeof (unsigned long) - 1)))) {
            // are there iov's after this one? Or is it so much we'd need
            // to do multiple writes anyway?
            if ((i + 1) < count || vecs[i].iov_len > 256) {
                // cop out and malloc
                unsigned long j;
                ssize_t sizetomalloc = 0, totvecsize = 0;
                unsigned char *cbuf, *cbufptr;

                for (j = i; j < count; j++)
                    totvecsize += vecs[j].iov_len;

                // pad up in case unaligned
                sizetomalloc = totvecsize + sizeof (int) - 1;
                sizetomalloc &= ~(sizeof (int) - 1);
                cbuf = (unsigned char *) malloc(sizetomalloc);
                // malloc returns aligned memory
                if (!cbuf) {
                    err = ENOMEM;
                    goto writev_out;
                }
                cbufptr = cbuf;
                for (j = i; j < count; j++) {
                    memcpy(cbufptr, vecs[j].iov_base,
                           vecs[j].iov_len);
                    cbufptr += vecs[j].iov_len;
                }
                err =
                    jffs2_flash_write(c, to, sizetomalloc,
                                      &thislen, cbuf);
                if (thislen > totvecsize) // in case it was aligned up
                    thislen = totvecsize;
                totlen += thislen;
                free(cbuf);
                goto writev_out;
            }
            else {
                // otherwise optimize for the common case
                int buf[256 / sizeof (int)]; // int, so int aligned
                size_t lentowrite;

                lentowrite = vecs[i].iov_len;
                // pad up in case its unaligned
                lentowrite += sizeof (int) - 1;
                lentowrite &= ~(sizeof (int) - 1);
                memcpy(buf, vecs[i].iov_base, lentowrite);

                err =
                    jffs2_flash_write(c, to, lentowrite,
                                      &thislen, (unsigned char *) &buf);
                if (thislen > vecs[i].iov_len)
                    thislen = vecs[i].iov_len;
            } // else
        }
        else
            err =
                jffs2_flash_write(c, to, vecs[i].iov_len, &thislen,
                                  (unsigned char *)vecs[i].iov_base);
        totlen += thislen;
        if (err || thislen != vecs[i].iov_len)
            break;
        to += vecs[i].iov_len;
    }
writev_out:
    if (retlen)
        *retlen = totlen;

    return err;
}

/*
 * Should check if it works correctly
 */
int jffs2_flash_erase(struct jffs2_sb_info * c,
                struct jffs2_eraseblock * jeb)
{
    assert(jeb);
    ECode ec;
    struct super_block *sb = OFNI_BS_2SFFJ(c);
    uint32_t offset = jeb->offset;
    MemoryBuf buf((Byte *)&offset, sizeof(uint32_t));

    //        D2(printf("FLASH ERASE ENABLED!!!\n"));
    //        D2(printf("erase address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + jeb->offset));
    //        D2(printf("size = %x\n", c->sector_size));

    ec = sb->s_dev->Control(FlashControl_EraseSector, buf, NULL, NULL);

    return ec;
}
